package com.vip.jwt.common.utils.sign;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.Digester;
import com.vip.jwt.common.exception.MyException;
import com.vip.jwt.common.utils.ConvertUtil;
import com.vip.jwt.common.utils.xcx.Constants;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.*;

import static com.vip.jwt.common.utils.xcx.Constants.USER_AGENT;

/**
 * 签名工具类
 */
public class WXSignUtil {

    /**
     * 获取随机串
     * @return
     */
    public static String getNonceStr(){
        return RandomUtil.randomString(16);
    }

    /**
     * 签名算法（MD5或者HMAC-SHA256）
     * @return
     */
    public static String getSignType(){
        return Constants.MD5;
    }

    /**
     * 获取当前时间戳
     * @return
     */
    public static String getTimeStamp(){
        return String.valueOf(DateUtil.date().getTime()/1000);
    }

    /**
     * 商户订单号
     * @return
     */
    public static String getOutTradeNo(){
        String dateStr = DateUtil.format(DateUtil.date(),"yyyyMMddHHmmssSSS");
        String randomStr = RandomUtil.randomNumbers(5);
        return dateStr+randomStr;
    }

    /**
     * 退款订单号
     * @return
     */
    public static String getOutRefundNo(){
        String dateStr = DateUtil.format(DateUtil.date(),"yyyyMMddHHmmssSSS");
        String randomStr = RandomUtil.randomNumbers(5);
        return dateStr+randomStr;
    }

    /**
     * 生成签名
     * @param data 待签名数据
     * @param key API密钥
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key){
        return generateSignature(data, key, Constants.MD5);
    }

    /**
     * 生成带有 sign 的 XML 格式字符串
     * @param data Map类型数据
     * @param key API密钥
     * @param signType 签名类型
     * @return 含有sign字段的XML
     */
    public static String generateSignedXml(final Map<String, String> data, String key, String signType){
        String sign = generateSignature(data, key, signType);
        data.put(Constants.FIELD_SIGN, sign);
        String s = null;
        try {
            s = ConvertUtil.mapToXml(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return s;
    }

    /**
     * 微信公众号->基本配置->填写服务器配置
     * 直接将此方法返回的字符串 return 出去即可
     * @param request
     * @return
     */
    public static String getEchostr(HttpServletRequest request,String token){
        String signature = request.getParameter("signature");
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        String echoStr = request.getParameter("echostr");
        List<String> list = new ArrayList<>();
        list.add(token);
        list.add(timestamp);
        list.add(nonce);
        Collections.sort(list);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 3; i++) {
            sb.append(list.get(i));
        }
        String listStr = sb.toString();
        Digester digester = SecureUtil.sha1();
        String hashcode = digester.digestHex(listStr.getBytes());
        if(hashcode.equals(signature)){
            return echoStr;
        }
        return "fail";
    }

    /**
     * 生成签名. 注意，若含有sign_type字段，必须和signType参数保持一致。
     * @param data 待签名数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名
     */
    private static String generateSignature(final Map<String, String> data, String key, String signType){
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals(Constants.FIELD_SIGN))
                continue;
            // 参数值为空，则不参与签名
            if (data.get(k).trim().length() > 0)
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("key=").append(key);
        System.out.println(sb.toString());
        if (Constants.MD5.equals(signType)) {
            try {
                return MD5(sb.toString()).toUpperCase();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
        }
        else if (Constants.HMACSHA256.equals(signType)) {
            try {
                return HMACSHA256(sb.toString(), key);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 判断签名是否正确，必须包含sign字段，否则返回false。使用MD5签名。
     * @param data Map类型数据
     * @param key API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key){
        return isSignatureValid(data, key, Constants.MD5);
    }

    /**
     * 判断签名是否正确，必须包含sign字段，否则返回false。
     * @param data Map类型数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名是否正确
     * @throws Exception
     */
    private static boolean isSignatureValid(Map<String, String> data, String key, String signType){
        if (!data.containsKey(Constants.FIELD_SIGN) ) {
            return false;
        }
        String sign = data.get(Constants.FIELD_SIGN);
        return Objects.equals(generateSignature(data, key, signType), sign);
    }

    /**
     * 生成 MD5
     * @param data 待处理数据
     * @return MD5结果
     */
    public static String MD5(String data) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(data.getBytes(StandardCharsets.UTF_8));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 生成 HMACSHA256
     * @param data 待处理数据
     * @param key 密钥
     * @return 加密结果
     * @throws Exception
     */
    private static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 带证书请求
     * @param url 请求地址
     * @param mchId 商户ID
     * @param xmlStr 内容
     * @return
     */
    public static String postSSLRequest(String url,String mchId,String xmlStr){
        BasicHttpClientConnectionManager connManager;
        // 将证书文件放在resources文件夹下
        // 证书文件获取方式：微信商户平台(pay.weixin.qq.com)-->账户中心-->账户设置-->API安全-->申请证书
        ClassPathResource cpr = new ClassPathResource("crt/apiclient_cert.p12");
        // 证书
        char[] password = mchId.toCharArray();
        InputStream certStream = cpr.getStream();
        try {
            KeyStore ks = KeyStore.getInstance("PKCS12");
            ks.load(certStream, password);
            // 实例化密钥库 & 初始化密钥工厂
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(ks, password);
            // 创建 SSLContext
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
                    sslContext,
                    new String[]{"TLSv1"},
                    null,
                    new DefaultHostnameVerifier());
            connManager = new BasicHttpClientConnectionManager(
                    RegistryBuilder.<ConnectionSocketFactory>create()
                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
                            .register("https", sslConnectionSocketFactory)
                            .build(),
                    null,
                    null,
                    null
            );
            HttpClient httpClient = HttpClientBuilder.create()
                    .setConnectionManager(connManager)
                    .build();
            HttpPost httpPost = new HttpPost(url);
            StringEntity postEntity = new StringEntity(xmlStr, "UTF-8");
            httpPost.addHeader("Content-Type", "text/xml");
            httpPost.addHeader("User-Agent", USER_AGENT + " " + mchId);
            httpPost.setEntity(postEntity);
            HttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            return EntityUtils.toString(httpEntity,"UTF-8");
        }catch (Exception e){
            e.printStackTrace();
            throw new MyException("操作失败，错误信息：【"+e.getMessage()+"】，请联系技术人员！");
        }
    }

}
